/**
 * ChessPiece.java
 * This class is a blueprint for the chess pieces that are extended from this
 * @author Tyler Eastman
 */
package bbchess.units;

import bbchess.units.pieces.Bishop;
import bbchess.units.pieces.King;
import bbchess.units.pieces.Queen;
import bbchess.units.pieces.Rook;
import java.awt.Image;
import java.awt.Point;
import java.awt.PointerInfo;
import java.util.ArrayList;
import javax.swing.JComponent;
import bbchess.util.SoundLoader;

public abstract class ChessPiece extends JComponent{
    // declare variables
    private String color;
    private int size;
    private int move;
    private String unit;
    private ChessSquare position;
    private Image sprite;

    // declare states
    private boolean dragging;
    private boolean captured;
    private boolean displayedMoves;
    private boolean moving;
    private boolean capturable;
    private boolean pinningKing;

    PointerInfo mousePointerInfo;
    Point mousePointer;

    private ArrayList<String> legalMoves;
    private ArrayList<String> capturableMoves;
    private ArrayList<String> threatenedMoves;
    private ArrayList<String> lastMoves;
    private ArrayList<String> kingCapturePath;
    private ArrayList<String> kingPinPath;
    private ArrayList<ChessPiece> threatenedBy;

    public ChessBoard board;
    
    public void initPiece (String color, ChessSquare position) {

        setMove(1);
        this.color = color;
        setPosition(position);
        setVisible(true);
        setDoubleBuffered(true);
        dragging = false;
        moving = false;
        captured = false;
        capturable = false;
        legalMoves = new ArrayList<String>();
        capturableMoves = new ArrayList<String>();
        threatenedMoves = new ArrayList<String>();
        lastMoves = new ArrayList<String>();
        threatenedBy = new ArrayList<ChessPiece>();
        kingCapturePath = null;
        kingPinPath = null;
        pinningKing = false;
    }

    /**
     * Sets the board reference for the chess piece.  To be called
     * after initialization.
     */
    public void setBoard() {
        board = (ChessBoard) getParent();
    }

    /**
     * Overridden version of setboard.  This one takes in the board to set
     * directly
     * @param board
     */
    public void setBoard(ChessBoard board) {
        this.board = board;
    }

    /*
     * This is a method that adjusts the legal and capturable moves of this piece
     * based on exterior factors, such as being pinned.
     */
    public void adjustMoves() {

        // check if pinned

        // if neither king is in check
        if (!board.getWhiteKing().isThreatened() && !board.getBlackKing().isThreatened()) {

            // if we're threateneds by any pieces
            if (!threatenedBy.isEmpty()) {

                // cycle through the pieces we're threatened by
                for (ChessPiece threateningPiece : getThreatenedBy()) {

                    // if we're threateningPiece by a queen, rook, or bishop
                    if ((threateningPiece instanceof Queen) ||
                            (threateningPiece instanceof Rook) ||
                            (threateningPiece instanceof Bishop)) {
                        
                        // if the king is not under threat by this piece, but will be if
                        // the threatenedPiece ignores this one, then we are the pinning piece
                        threateningPiece.calculateKingPinMoves(this);
                        if (threateningPiece.getPinningKing()) {

                            // We're pinned to the king!
                            // Restrict available legal and capture moves to the threateningPieces
                            // getKingPinPath()


                            // start an ArrayList of moves to remove from pinned piece
                            ArrayList<String> movesToRemove = new ArrayList<String>();

                            for (String legalMove : legalMoves) {
                                // if the piece contains legal moves outside of the kingPinPath
                                // then add them to the movesToRemove ArrayList
                                if (!threateningPiece.getKingPinPath().contains(legalMove)) {
                                    movesToRemove.add(legalMove);
                                }
                            }
                            for (String captureMove : capturableMoves) {
                                // if the piece contains capturable moves outside of the kingPinPath
                                // then add them to the movesToRemove ArrayList
                                if (!threateningPiece.getKingPinPath().contains(captureMove)) {
                                    movesToRemove.add(captureMove);
                                }
                            }

                            // loop through the legal and capturablemove ArrayLists and remove
                            // any moves contained in the movesToRemove ArrayList
                            for (String moveToRemove : movesToRemove) {
                                if (legalMoves.contains(moveToRemove)) {
                                    legalMoves.remove(moveToRemove);
                                }
                                if (capturableMoves.contains(moveToRemove)) {
                                    capturableMoves.remove(moveToRemove);
                                }
                            }
                        }
                    }
                }
            }
        }

 // else if the king is in check, restrict moves to only the ones that will get the king out of check!
        else {
            // we know that a king is threatened.  Therefore in order to find out whose
            // turn it is, we simply need to know the color of this piece.

            ChessPiece king = null;

            // white's turn
            if (getColor().equals("white")) {
                king = board.getWhiteKing();
            } else {
                king = board.getBlackKing();
            }

            // can the check can be intercepted?
            if ((king.getThreatenedBy().size() == 1)) {

                ChessPiece threateningPiece = king.getThreatenedBy().get(0);
                ArrayList<String> capturePath = king.getThreatenedBy().get(0).getKingCapturePath();
                ArrayList<String> movesToRemove = new ArrayList<String>();

                // if the threateningPiece is something other than a rook, bishop, or queen,
                // it's not going to have a capturePath.
                if (capturePath != null) {
                    // restrict legalMoves to capturePath
                    for (String legalMove : legalMoves) {
                        // if the piece contains legal moves outside of the kingPinPath
                        // then add them to the movesToRemove ArrayList
                        if (!capturePath.contains(legalMove)) {
                            movesToRemove.add(legalMove);
                        }
                    }
                }

                else {
                    legalMoves.clear();
                }

                // restrict captureableMoves to threateningPiece
                for (String captureMove : capturableMoves) {
                    if (!captureMove.equals(threateningPiece.getPosition().getPosition())) {
                        movesToRemove.add(captureMove);
                    }
                }

                // loop through the legal and capturablemove ArrayLists and remove
                // any moves contained in the movesToRemove ArrayList
                for (String moveToRemove : movesToRemove) {
                    if (legalMoves.contains(moveToRemove)) {
                        legalMoves.remove(moveToRemove);
                    }
                    if (capturableMoves.contains(moveToRemove)) {
                        capturableMoves.remove(moveToRemove);
                    }
                }
            }
        }
    }

    @Override
    public String toString() {
        String output = this.getColor() + " " + this.getUnit() + this.getPosition().getPosition();
        return output;
    }

    /*
     * Determines if a certain square (move) contains a king of the opposite color. This is used
     * in Rook, Bishop, and Queen's move calculations, to determine a direct path of threat for
     * a king.
     */
    public boolean moveContainsKing(String move) {
        try {
            ChessPiece squareContents = board.getChessSquares().get(move).getContents();
            if (squareContents != null) {
                if ((squareContents instanceof King) && !(squareContents.getColor().equals(getColor()))) {
                    return true;
                }
            }
        } catch (Exception ex) {/* null pointer, return false */}
        return false;
    }

    /*
     * setKingCapturePath is used for bishops, rooks, and queens to save the path
     * of threat for an opposing king.  This is useful to find if a piece can
     * intercept this path
     */
    public void setKingCapturePath(ArrayList<String> kingCapturePath) {
        this.kingCapturePath = kingCapturePath;
    }

    /*
     * sets the kingPinPath.  Used in calculating pin's
     */
    public void setKingPinPath(ArrayList<String> kingPinPath) {
        this.kingPinPath = kingPinPath;
    }

    /*
     * adds a position to the kingPin ArrayList
     */
    public void addKingPinPath(String newPosition) {
        kingPinPath.add(newPosition);
    }

    public void calculateKingPinMoves(ChessPiece piece) {
        // only implement if piece is a rook, bishop, or queen
    }

    public boolean getPinningKing() {
        return pinningKing;
    }

    public void setPinningKing(boolean choice) {
        pinningKing = choice;
    }

    public ArrayList<String> getKingPinPath() {
        return kingPinPath;
    }

    /*
     * getKingCapturePath is used for bishops, rooks, and queens to return the path
     * of threat for an opposing king.  This is useful to find if a piece can
     * intercept this path
     */
    public ArrayList<String> getKingCapturePath() {
        return kingCapturePath;
    }

    public void addThreatenedBy(ChessPiece threat) {
        threatenedBy.add(threat);
    }

    public void clearThreatenedBy() {
        threatenedBy.clear();
    }

    public void updateThreatenedPieces() {
        for (String threatenedSquare : getCapturableMoves()) {
            ChessPiece contents = null;
            contents = board.getChessSquares().get(threatenedSquare).getContents();

            if (contents != null) {
                contents.addThreatenedBy(this);
            }
        }
    }

    public boolean isThreatened() {
        return (!threatenedBy.isEmpty());
    }

    public ArrayList<ChessPiece> getThreatenedBy() {
        return threatenedBy;
    }

    public void updateMoves() {
        clearLegalMoves();
        clearCapturableMoves();
        clearThreatenedMoves();
        calculateThreatenedMoves();
        calculateCaptureMoves();
        calculateLegalMoves();
        updateThreatenedPieces();
    }

    // Abstract, initialize legal moves
    public abstract void calculateLegalMoves();

    // Abstract, initialize capture moves
    public abstract void calculateCaptureMoves();

    public abstract void calculateThreatenedMoves();

    // clear list of legal moves
    public void clearLegalMoves() {
        legalMoves.clear();
    }

    // clear list of capturable moves
    public void clearCapturableMoves() {
        capturableMoves.clear();
        if (kingCapturePath != null) {
            kingCapturePath.clear();
        }
    }

    public void clearThreatenedMoves() {
        threatenedMoves.clear();
    }

    // clear list of last moves
    public void clearLastMoves() {
        lastMoves.clear();
    }

    /**
     * @return the sprite
     */
    public Image getSprite() {
        return sprite;
    }

    public void setCapturable(boolean choice) {
        capturable = choice;
    }

    public boolean getCapturable() {
        return capturable;
    }

    public void tryToPromote() {
        // implement only in pawns
    }

    public boolean isMoving() {
        return moving;
    }

    public void setMoving(boolean choice) {
        moving = choice;

        if (choice == false) {
            // play drop sound
            SoundLoader.drop();
        }
    }

    public void setAllMoves(boolean choice) {
        if (choice) {
            setCurrentSquare(true);
            setCapturableMoves(true);
            setLegalMoves(true);
            setThreatenedMoves(true);
        } else {
            setCurrentSquare(false);
            setCapturableMoves(false);
            setLegalMoves(false);
            setThreatenedMoves(false);
        }
    }

    /**
     * @param sprite the sprite to set
     */
    public void setSprite(Image sprite) {
        this.sprite = sprite;
    }

    /**
     * @return the position
     */
    public ChessSquare getPosition() {
        return position;
    }

    /**
     * @param position the position to set
     */
    public void setPosition(ChessSquare position) {
        this.position = position;
        position.setContents(this);
    }

    /**
     * Displays legal moves for given chesspiece
     * @param choice
     */
    public void showLegalMoves(boolean choice) {
        if (choice) {
            setAllMoves(true);
        } else {
            setAllMoves(false);
        }
    }

    /**
     * @return the color
     */
    public String getColor() {
        return color;
    }

    /**
     * @return the unit
     */
    public String getUnit() {
        return unit;
    }

    /**
     * @param unit the unit to set
     */
    public void setUnit(String unit) {
        this.unit = unit;
    }

    /**
     * @return the dragging
     */
    public boolean isDragging() {
        return dragging;
    }

    /**
     * @return the legalMoves
     */
    public ArrayList<String> getLegalMoves() {
        return legalMoves;
    }

    private ArrayList<String> getThreatenedMoves() {
        return threatenedMoves;
    }

    private ArrayList<String> getLastMoves() {
        return lastMoves;
    }

    /**
     * Add an ArrayList of moves to the legalMoves ArrayList
     * @param legalMoves the legalMoves to set
     */
    public void addLegalMoves(ArrayList<String> legalMoves) {
        try {
            this.legalMoves.addAll(legalMoves);
        } catch (Exception ex) {  }
    }

    /**
     * Add a move to the legalMoves ArrayList
     * @param legalMove The move to set
     */
    public void addLegalMove(String legalMove) {
        try {
            this.legalMoves.add(legalMove);
        } catch (Exception ex) {  }
    }

    // add capturable move to arraylist capturableMoves
    public String addCapturableMove(String capturableMove) {
        if (capturableMove != null) {
            this.capturableMoves.add(capturableMove);
            return capturableMove;
        }
        return "";
    }

    // add threateningPiece move to arraylist threatenedMoves
    public void addThreatenedMoves(ArrayList<String> threatenedMoves) {
        try {
            this.threatenedMoves.addAll(threatenedMoves);
        } catch (Exception ex) {  }
    }

    // add last move to arraylist lastMoves
    public void addLastMoves(String lastMove) {
        if (lastMove != null) {
            this.lastMoves.add(lastMove);
        }
    }

    /**
     * Sets if we should highlight the current square
     * @param choice
     */
    private void setCurrentSquare(boolean choice) {
        // if we should highlight legal moves
        if (choice) {
            board.setCurrentSquare(this.getPosition().getPosition(), true);
        } else {
            try {
                board.setCurrentSquare(this.getPosition().getPosition(), false);
            } catch (Exception ex) {  }
        }
    }

    /**
     * Calls chessBoard to display the legal moves for given piece
     * @param legal
     */
    private void setLegalMoves(boolean legal) {
        // if we should highlight legal moves
        if (legal) {
            for (String legalMove : getLegalMoves()) {
                board.setLegalMoveSquares(legalMove, true);
            }
        } else {
            try {
                for (String legalMove : getLegalMoves()) {
                    board.setLegalMoveSquares(legalMove, false);
                }
            } catch (Exception ex) {  }
        }
    }

    private void setCapturableMoves(boolean choice) {
        for (String capturableMove : getCapturableMoves()) {
            if (choice) {
                board.addToCapturable(capturableMove, this);
            }

            ChessPiece piece = board.getChessSquares().get(capturableMove).getContents();
            
            if (piece != null) {
                piece.setCapturable(choice);
            }

            // else if there is no piece at capturable location, it must be an enpassant
            // capturable square
            else {
                board.getChessSquares().get(capturableMove).setEnPassantCapturable(choice);
            }
        }
    }

    public void setThreatenedMoves(boolean threatened) {
        // if we should highlight threateningPiece moves
        if (threatened) {
            for (String threatenedMove : getThreatenedMoves()) {
                board.setSquareThreatened(threatenedMove, true);
            }
        } else {
            try {
            // make sure board isn't null
                for (String threatenedMove : getThreatenedMoves()) {
                    board.setSquareThreatened(threatenedMove, false);
                }
            } catch (Exception ex) {  }
        }
    }

    public void setLastMoves(boolean showLastMove) {
        // if we should highlight last moves
        if (showLastMove) {
            for (String lastMove : getLastMoves()) {
                board.setSquareLastMove(lastMove, true);
            }
        } else {
            try {
            // make sure board isn't null
                for (String lastMove : getLastMoves()) {
                    board.setSquareLastMove(lastMove, false);
                }
            } catch (Exception ex) {  }
        }
    }

    /**
     * @return the move
     */
    public int getMove() {
        return move;
    }

    /**
     * @param move the move to set
     */
    public void setMove(int move) {
        this.move = move;
    }

    /**
     * @return the capturableMoves
     */
    public ArrayList<String> getCapturableMoves() {
        return capturableMoves;
    }

    public void capture(boolean choice) {
        if (choice == true) {
            board.captureChessPiece(this);
            captured = true;
            setSize(30, 30);
            size = 30;
        }
    }

    /**
     * @return the captured
     */
    public boolean isCaptured() {
        return captured;
    }
}
